﻿// Enumerator for a vector.
//
// Copyright (C) 2012 M. de Wolde
//
// This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
// Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License along with this program. If not, see
// <http://www.gnu.org/licenses/>.
//
// 2012-06-17 MIWO Created.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

namespace Lemody.Glu {

/// <summary>Enumerator for a vector.</summary>
/// <remarks>
///     The enumerator supports stepping forward, stepping backward, positioning at an index, and updating the
///     enumerated element.
/// </remarks>
public class VectorEnumerator<T>: IEnumerator<T>
{
    // Fields.
    // -----------------------------------------------------------------------------------------------------------------

    /// <summary>Vector for which the enumerator is created.</summary>
    private Vector<T> _vector;

    /// <summary>The enumerator position is defined by a reference to a vector node.</summary>
    private VectorNode<T> _vectorNode;

    /// <summary>Update count, used to detect usage of the enumerator after the vector has been modified.</summary>
    private Int32 _updateCount;

    /// <summary>True if the enumerator addresses a fictive element just before the first element.</summary>
    /// <remarks>The vector node addresses the first element, or is null if the vector is empty.</remarks>
    private bool _atReverseEnd;

    /// <summary>True if the enumerator addresses an element, that is however considered fictive.</summary>
    /// <remarks>
    ///     The vector node addresses the element. The element is yet considered fictive, and a <see cref="MoveNext"/>
    ///     or <see cref="MovePrevious"/> is required to make the element accessible. The fictive indicator is not set
    ///     if the vector node is null because the vector is empty; in that case the <see cref="_atEnd"/> and <see
    ///     cref="_atReverseEnd"/> indicators are set.
    /// </remarks>
    private bool _isFictive;

    /// <summary>True if the enumerator addresses a fictive element just beyond the last element.</summary>
    /// <remarks>The vector node addresses the last element, or is null if the vector is empty.</remarks>
    private bool _atEnd;

    // Object management.
    // -----------------------------------------------------------------------------------------------------------------

    /// <summary>Constructor.</summary>
    /// <remarks>
    ///     The enumerator is positioned at a fictive element just before the first element of the vector, such that a
    ///     <see cref="MoveNext"/> is required to step to the first element.
    /// </remarks>
    /// <param name="vector">I: Vector for which the enumerator is created.</param>
    internal VectorEnumerator(Vector<T> vector)
    {
        Debug.Assert(vector != null);
        _vector = vector;
        Reset();
    }

    /// <summary>Constructor.</summary>
    /// <remarks>
    ///     The constructor results in an enumerator positioned at the element with the specified index, marking the
    ///     element as fictive. Either a <see cref="MovePrevious"/> or <see cref="MoveNext"/> will clear the fictive
    ///     indicator and make the element accessible through <see cref="Current"/>.
    /// </remarks>
    /// <param name="vector">I: Vector for which the enumerator is created.</param>
    /// <param name="index">
    ///     I: Index of the element where to position. -1 can be used to position at a fictive element just before the
    ///     first element. The vector element count can be used to position at a fictive element just beyond the last
    ///     element. An index from 0 included to the vector element count excluded positions at the element, marking it
    ///     as fictive.
    /// </param>
    internal VectorEnumerator(Vector<T> vector, int index)
    {
        Debug.Assert(vector != null);
        _vector = vector;
        Reset(index);
    }

    /// <summary>Destructor.</summary>
    ~VectorEnumerator()
    {
        Dispose(false);
    }

    /// <summary>Disposes the enumerator.</summary>
    public void Dispose()
    {
        Dispose(true);
    }

    /// <summary>Implementation of the dispose.</summary>
    protected virtual void Dispose(bool disposing)
    {
        //
    }

    /// <summary>Validates that the enumerated vector is not modified.</summary>
    /// <exception cref="InvalidOperationException"></exception>
    private void Validate()
    {
        if (_updateCount != _vector.UpdateCount)
        {
            throw new InvalidOperationException("A vector enumerator is no longer valid because the enumerated vector has been modified.");
        }
    }

    // IEnumerator interface.
    // -----------------------------------------------------------------------------------------------------------------

    /// <summary>Returns the element currently addressed by the enumerator.</summary>
    /// <remarks>The enumerator must currently address an element.</remarks>
    public T Current
    {
        [DebuggerStepThrough]
        set
        {
            Debug.Assert(!_atReverseEnd && !_isFictive && !_atEnd);
            _vectorNode.Element = value;
        }
        [DebuggerStepThrough]
        get
        {
            Debug.Assert(!_atReverseEnd && !_isFictive && !_atEnd);
            return _vectorNode.Element;
        }
    }

    /// <summary>Returns the element currently addressed by the enumerator.</summary>
    /// <remarks>The enumerator must currently address an element.</remarks>
    object IEnumerator.Current
    {
        [DebuggerStepThrough]
        get
        {
            return Current;
        }
    }

    /// <summary>Resets the enumerator to a position at a fictive element just before the first element of the vector.</summary>
    public void Reset()
    {
        _updateCount = _vector.UpdateCount;
        if (_vector.Count == 0)
        {
            _vectorNode = null;
            _atReverseEnd = true;
            _isFictive = false;
            _atEnd = true;
        }
        else
        {
            _vectorNode = _vector.GetFirstNode();
            _atReverseEnd = true;
            _isFictive = false;
            _atEnd = false;
        }
    }

    /// <summary>Moves the enumerator to the next element.</summary>
    /// <remarks>
    ///     <para>
    ///         The enumerator can address a fictive element just before the first element, and will address the first
    ///         element after the move. This is the case after a <see cref="Reset()"/>.
    ///     </para>
    ///     <para>
    ///         The enumerator can address a vector element and will address the next element after the move.
    ///     </para>
    ///     <para>
    ///         The enumerator can address the last element of the vector, and will address a fictive element just
    ///         beyond the last element after the move. It is then possible to perform a <see cref="MovePrevious"/>
    ///         which will again address the last element.
    ///     </para>
    ///     <para>
    ///         The enumerator can address an actual element, however marked as fictive. The element will be no longer
    ///         marked as fictive after the move, and can be accessed using <see cref="Current"/>.
    ///     </para>
    ///  </remarks>
    public bool MoveNext()
    {
        Validate();
        bool atElement;
        if (_vectorNode == null)
        {
            atElement = false;
        }
        else
        {
            if (_atReverseEnd)
            {
                _atReverseEnd = false;
                atElement = true;
            }
            else if (_isFictive)
            {
                _isFictive = false;
                atElement = true;
            }
            else if (_atEnd)
            {
                atElement = false;
            }
            else
            {
                VectorNode<T> next = _vectorNode.GetNext();
                if (next == null)
                {
                    _atEnd = true;
                    atElement = false;
                }
                else
                {
                    _vectorNode = next;
                    atElement = true;
                }
            }
        }
        return atElement;
    }

    // Support in addition to the IEnumerator interface.
    // -----------------------------------------------------------------------------------------------------------------

    /// <summary>Moves the enumerator to the previous element.</summary>
    /// <remarks>
    ///     <para>
    ///         The enumerator can address a fictive element just beyond the last element, and will address the last
    ///         element after the move.
    ///     </para>
    ///     <para>
    ///         The enumerator can address a vector element and will address the previous element after the move.
    ///     </para>
    ///     <para>
    ///         The enumerator can address the first element of the vector, and will address a fictive element just
    ///         before the first element after the move. It is then possible to perform a <see cref="MoveNext"/> which
    ///         will again address the first element.
    ///     </para>
    ///     <para>
    ///         The enumerator can address an actual element, however marked as fictive. The element will be no longer
    ///         marked as fictive after the move, and can be accessed using <see cref="Current"/>.
    ///     </para>
    ///  </remarks>
    public bool MovePrevious()
    {
        Validate();
        bool atElement;
        if (_vectorNode == null)
        {
            atElement = false;
        }
        else
        {
            if (_atReverseEnd)
            {
                atElement = false;
            }
            else if (_isFictive)
            {
                _isFictive = false;
                atElement = true;
            }
            else if (_atEnd)
            {
                _atEnd = false;
                atElement = true;
            }
            else
            {
                VectorNode<T> previous = _vectorNode.GetPrevious();
                if (previous == null)
                {
                    _atReverseEnd = true;
                    atElement = false;
                }
                else
                {
                    _vectorNode = previous;
                    atElement = true;
                }
            }
        }
        return atElement;
    }

    /// <summary>Resets the enumerator to the element at the specified index, marking the element as fictive.</summary>
    /// <param name="index">
    ///     I: Index of the element where to position. -1 can be used to position at a fictive element just before the
    ///     first element. The vector element count can be used to position at a fictive element just beyond the last
    ///     element. An index from 0 included to the vector element count excluded positions at the element, marking it
    ///     as fictive.
    /// </param>
    private void Reset(int index)
    {
        _updateCount = _vector.UpdateCount;
        int count = _vector.Count;
        Debug.Assert(index >= -1 && index <= count);
        if (count == 0)
        {
            _vectorNode = null;
            _atReverseEnd = true;
            _isFictive = false;
            _atEnd = true;
        }
        else
        {
            if (index == -1)
            {
                _vectorNode = _vector.GetFirstNode();
                _atReverseEnd = true;
                _isFictive = false;
                _atEnd = false;
            }
            else if (index == count)
            {
                _vectorNode = _vector.GetLastNode();
                _atReverseEnd = false;
                _isFictive = false;
                _atEnd = true;
            }
            else
            {
                _vectorNode = _vector.GetNode(index);
                _atReverseEnd = false;
                _isFictive = true;
                _atEnd = false;
            }
        }
    }
}

} // namespace
